/*
 * Copyright 2008. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the Apache License, Version 2.0. You
 * can find a copy of the license at:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */

package com.sinai.mshab.widgets;

import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ChangeListenerCollection;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.ClickListenerCollection;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.SourcesChangeEvents;
import com.google.gwt.user.client.ui.SourcesClickEvents;
import com.google.gwt.user.client.ui.Widget;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * This creates a collection of radio buttons given some choices in a String[].
 * This is an abstract class meant to be overridden.
 * 
 * This class also provides an easy way to get to individual radios based on
 * their name.
 * 
 * @author Sergey Vesselov
 */
public abstract class AbstractRadioButtonGroup extends Composite implements
		HasValue<String>, HasText, SourcesClickEvents, SourcesChangeEvents {

	/**
	 * A map of question to radio button.
	 */
	protected Map<String, RadioButton> choiceToRadio = new HashMap<String, RadioButton>();

	/**
	 * The main panel.
	 */
	protected Panel mainPanel;

	/**
	 * The collection of {@link ClickListener}s on this
	 * <code>AbstractRadioButtonGroup</code>.
	 */
	protected ClickListenerCollection clickListeners = new ClickListenerCollection();

	/**
	 * The collection of {@link ChangeListener}s on this
	 * <code>AbstractRadioButtonGroup</code>.
	 */
	protected ChangeListenerCollection changeListeners = new ChangeListenerCollection();

	/**
	 * Creates a new collection of {@link RadioButton}s given
	 * <code>choices</code> and a common <code>name</code> and a
	 * <code>panel</code> for layout. If any arguments are null or the name is
	 * empty, an {@link IllegalArgumentException} is thrown.
	 * 
	 * @param name
	 *            the none null and none empty name of the collection.
	 * @param choices
	 *            list of choices offered in the collection.
	 * @param panel
	 *            the concrete {@link Panel} to use for the collection.
	 */
	public AbstractRadioButtonGroup(String name, String[] choices, Panel panel) {

		// check arguments.
		if (name == null || name.equals("") || choices == null || panel == null) {
			throw new IllegalArgumentException("Constructor arguments to "
					+ getClass().getName() + " must all be instantiated.");
		}

		this.mainPanel = panel;

		// make a new radio button for each choice we are given
		ClickListener listener = new ClickListenerBlubbling();
		for (String choice : choices) {

			// create the radio button, add some style to it and add our custom
			// click listener to catch clicks.
			RadioButton radio = new RadioButton(name, choice);
			radio.addStyleDependentName("radioButtonGroup");
			radio.addClickListener(listener);
			choiceToRadio.put(choice, radio);
			mainPanel.add(radio);
		}
		initWidget(mainPanel);

		// set the style.
		setStylePrimaryName("mshab-RadioButtonGroup");
	}

	/**
	 * Returns a reference of the radio button with the provided choice
	 * 
	 * @param choice
	 *            the choice of the button to be returned
	 * @return the radio button with specified choice.
	 */
	public RadioButton getRadio(String choice) {
		return choiceToRadio.get(choice);
	}

	/**
	 * Returns true if at least one radio in this group is checked.
	 * 
	 * @return true if at least one radio in this group is checked.
	 */
	public boolean isChecked() {
		boolean set = false;

		// if any are checked, that means the group is checked.
		for (RadioButton radio : choiceToRadio.values()) {
			if (radio.isChecked()) {
				set = true;
				break;
			}
		}
		return set;
	}

	/**
	 * Return the value of the selected radio button.
	 * 
	 * @return the value of the selected radio button.
	 */
	public String getValue() {
		String result = null;

		// find the radio button that's checked, and get it's text.
		for (Entry<String, RadioButton> element : choiceToRadio.entrySet()) {
			if (element.getValue().isChecked()) {
				result = element.getKey();
				break;
			}
		}

		return result;
	}

	/**
	 * Checks the {@link RadioButton} with the text set to <code>value</code>.
	 * 
	 * @param value
	 *            the text of the {@link RadioButton} to check.
	 */
	public void setValue(String value) {
		RadioButton buttonToCheck = choiceToRadio.get(value);

		// check buttonToCheck and uncheck the previous one.
		if (buttonToCheck != null) {
			RadioButton checked = getRadio(getValue());
			if (checked != null) {
				checked.setChecked(false);
			}

			buttonToCheck.setChecked(true);
		}
	}

	public String getText() {

		// just use the value methods.
		return getValue();
	}

	public void setText(String value) {

		// just use the value methods.
		setValue(value);
	}

	/**
	 * Notifies all the {@link ChangeListener}s listening to this
	 * <code>AbstractRadioButtonGroup</code>.
	 */
	protected void notifyChangeListeners() {
		for (ChangeListener changeListener : changeListeners) {
			changeListener.onChange(this);
		}
	}

	/**
	 * Returns the number of {@link RadioButton} this collection has.
	 * 
	 * @return the number of {@link RadioButton} this collection has.
	 */
	public int size() {
		return choiceToRadio.size();
	}

	public void addClickListener(ClickListener listener) {
		clickListeners.add(listener);
	}

	public void removeClickListener(ClickListener listener) {
		clickListeners.remove(listener);
	}

	public void addChangeListener(ChangeListener listener) {
		changeListeners.add(listener);
	}

	public void removeChangeListener(ChangeListener listener) {
		changeListeners.remove(listener);
	}

	/**
	 * This {@link ClickListener} catches clicks on the containing
	 * {@link RadioButton}s and updates the listeners listening to the
	 * <code>AbstractRadioButtonGroup</code> that a click occurred.
	 * 
	 * @author Arthur Kalmenson
	 */
	private class ClickListenerBlubbling implements ClickListener {

		public void onClick(Widget widget) {

			// we caught the clicks that happen to the contained radio buttons,
			// now we need to forward them to the click listeners.
			for (ClickListener listener : clickListeners) {
				listener.onClick(AbstractRadioButtonGroup.this);
			}

			// notify our change listeners that something changed.
			notifyChangeListeners();
		}
	}
}
